package org.gpp.proj1.communication.processor;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ExportException;
import java.rmi.server.ServerNotActiveException;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import org.gpp.proj1.communication.CommunicationException;
import org.gpp.proj1.communication.listener.RequestListener;
import org.gpp.proj1.communication.protocol.Message;
import org.gpp.proj1.communication.sender.RMIRequestSender;
import org.gpp.proj1.communication.sender.manager.RequestSenderManagerIF;

import com.jme.util.LoggingSystem;

public class RMIRequestProcessor implements RequestProcessorIF {

	private static final long serialVersionUID = 1279930078895367217L;
	
	private final RequestSenderManagerIF rsManager;

	private final int port;

	private List<RequestListener> requestListeners;
	
	public RMIRequestProcessor( RequestSenderManagerIF rpManager, int port ) throws RemoteException {
		
		this.rsManager = rpManager;
		this.port = port;
		
		this.requestListeners = new LinkedList<RequestListener>();
	}
	
	/**
	 * Binds this remote object to the RMI registry
	 * @throws CommunicationException 
	 * @throws RemoteException 
	 */
	public void startup() throws CommunicationException {
		String name = RequestProcessorIF.REQUEST_PROCESSOR;
		try {
			RequestProcessorIF stub = (RequestProcessorIF) UnicastRemoteObject.exportObject(this, 0);
		
			Registry registry;
			try{ 				
				registry = LocateRegistry.createRegistry( port );
			} catch( ExportException e ) {
				registry = LocateRegistry.getRegistry( port );
			}
			
			registry.rebind(name, stub);
			LoggingSystem.getLogger().info( "RequestProcessor bound." );
		} catch( RemoteException e ) {
			throw new CommunicationException( "Could not bind RequestProcessor Instance.", e );
		}
	}
	
	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.processor.RequestProcessorIF#connect(java.lang.String, java.lang.String, int)
	 */
	public String connect( String serverId, int clientPort ) throws RemoteException {		
		try {
			// Retrieves the client's request processor
			String rmiAddress = "rmi://" + UnicastRemoteObject.getClientHost() + ":" + clientPort + "/" + REQUEST_PROCESSOR;
			RequestProcessorIF requestProcessor = (RequestProcessorIF) Naming.lookup( rmiAddress );
			
			// Generated the client ID
			String clientId = UUID.randomUUID().toString();		
			
			// Adds a sender representing an outgoing connection to the client
			this.rsManager.notifyConnected( clientId, new RMIRequestSender( serverId, clientId, requestProcessor ) );
			
			// Returns the client's id 
			return clientId;
		} catch (MalformedURLException e) {
			throw new RemoteException("Invalid client id", e);
		} catch (NotBoundException e) {
			throw new RemoteException("Client request processor isn't bound", e);
		} catch (ServerNotActiveException e) {
			e.printStackTrace();
			throw new RemoteException("Could not get client address", e);
		}
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.processor.RequestProcessorIF#disconnect(java.lang.String)
	 */
	public void disconnect(String clientUUId) throws RemoteException {

		if( !rsManager.contains( clientUUId ) ) {
			throw new RemoteException( "Not connected" );
		}
		
		rsManager.notifyDisconnected( clientUUId );
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.processor.RequestProcessorIF#ping()
	 */
	public void ping() throws RemoteException {

		return;
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.processor.RequestProcessorIF#receive(java.lang.String, java.lang.String[])
	 */
	public void receive( Message message ) throws RemoteException {

		if( ! this.rsManager.contains( message.getSenderId() ) ) {
			throw new RemoteException( "Not connected" );
		}

		synchronized( this.requestListeners ) {
			for( RequestListener listener: this.requestListeners ) {
				listener.requestReceived( message );
			}	
		}
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.processor.RequestProcessorIF#stop()
	 */
	public void stop() {
		this.rsManager.notifyDisconnected();
			
		try{
			String name = RequestProcessorIF.REQUEST_PROCESSOR;
			Registry registry = LocateRegistry.getRegistry( port );
			registry.unbind( name );				
		} catch (NotBoundException e) {
			LoggingSystem.getLogger().warning( "RequestProcessor instance was already unbound." );
		} catch (RemoteException e) {
			LoggingSystem.getLogger().warning( "Could not unbind request processor;" );
		}
		
		LoggingSystem.getLogger().info( "RequestProcessor stopped." );
	}

	/**
	 * Regists a request listener, that will be notified whenever 
	 * a request arrives at the processor
	 * 
	 * @param listener
	 */
	public void registerListener(RequestListener listener) {
		
		synchronized( this.requestListeners ) {
			this.requestListeners.add( listener );
		}
	}

	/**
	 * Removes a list from the processor's listener list
	 * @param listener
	 */
	public void unregisterListener(RequestListener listener) {

		synchronized( this.requestListeners ) {			
			this.requestListeners.remove( listener );
		}
	}
}
